<template>
  <div class="tree-graph-container">
    <div class="flex">
      <el-button type="primary" @click="handleExportPng">
        <SvgIcon name="xz" color="#ffffff"></SvgIcon>
        <span style="padding-left: 8px">导出Png</span>
      </el-button>
      <el-button type="primary" @click="handleExportSvg">
        <SvgIcon name="xz" color="#ffffff"></SvgIcon>
        <span style="padding-left: 8px">导出Svg</span>
      </el-button>
    </div>

    <div class="flex flex-row">
      <ToolBar @enlarge="onEnlarge" @narrow="onNarrow" @fullScreen="onFullScreen" />
      <!-- 1. x6 容器 -->
      <div id="x6Container" ref="containerRef"></div>

      <!--1、右侧工具栏 start-->
      <el-col :span="4" class="h-full">
        <el-form :model="formState">
          <el-form-item label="标题" v-show="formState.labelText !== null">
            <el-input v-model:value="formState.labelText" @change="onNameChange" />
          </el-form-item>

          <el-form-item label="用户" v-show="formState.userId !== null">
            <el-select v-model:value="formState.userId" placeholder="请选择用户" @change="onUserIdChange">
              <el-option :value="1">张三</el-option>
              <el-option :value="2">李四 </el-option>
            </el-select>
          </el-form-item>

          <el-form-item label="角色" v-show="formState.roleId !== null">
            <el-select v-model:value="formState.roleId" placeholder="请选择角色" @change="onRoleIdChange">
              <el-option :value="1"> 管理员1 </el-option>
              <el-option :value="2"> 管理员2 </el-option>
            </el-select>
          </el-form-item>
        </el-form>
      </el-col>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref, shallowRef, reactive, UnwrapRef } from "vue";
import { Graph, DataUri, Cell } from "@antv/x6";
import { toggleFullScreen } from "@/utils/fullScreen";
import { Export } from "@antv/x6-plugin-export";

let graph: Graph | null = null;

let curCel: Cell | null = null;

// x6 图的实例
const graphInstance = shallowRef();

const containerRef = ref();

// formState 定义
interface FormState {
  labelText: any;
  userId: any;
  roleId: any;
}

const formState: UnwrapRef<FormState> = reactive({
  labelText: "",
  userId: "",
  roleId: ""
});

// label/text设置
const onNameChange = () => {
  // cell中的attr参数赋值
  curCel?.attr("label/text", formState.labelText);

  // rect.attr('label/text', 'hello');
  // // 等同于
  // rect.attr('label', {
  //   text: 'hello'
  // });
  // // 等同于
  // rect.attr({
  //   label: {
  //     text: 'hello'
  //   }
  // });
  // // 当传入的属性值为 null 时可以移除该属性。
  // rect.attr('label/text', null);
};

// data中的userId设置
const onUserIdChange = () => {
  curCel?.setData({
    userId: formState.userId
  });
};
// data中的roleId设置
const onRoleIdChange = () => {
  curCel?.setData({
    roleId: formState.roleId
  });
};

// 自定义节点
Graph.registerNode("custom-rect", {
  inherit: "rect", // 继承基础矩形
  width: 200,
  height: 60,
  markup: [
    {
      tagName: "rect", // 标签名称
      selector: "body" // 选择器
    },
    {
      tagName: "image",
      selector: "img"
    },
    {
      tagName: "text",
      selector: "label"
    },
    {
      tagName: "text",
      selector: "text"
    }
  ],
  attrs: {
    body: {
      stroke: "#8f8f8f",
      strokeWidth: 1,
      fill: "#fff",
      rx: 6,
      ry: 6
    },
    img: {
      "xlink:href": "https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png",
      width: 16,
      height: 16,
      x: 12,
      y: 12
    },
    label: {
      text: "Node",
      refX: 40,
      refY: 14,
      fill: "rgba(0,0,0,0.85)",
      fontSize: 12,
      "text-anchor": "start"
    },
    rect: {
      class: "markupTest05Rect",
      fill: "#fff",
      rx: 3,
      ry: 3,
      refWidth: 1,
      refHeight: 1,
      refX: 0,
      refY: 0
    },
    text: {
      text: "this is content text",
      refX: 40,
      refY: 38,
      fontSize: 12,
      fill: "rgba(0,0,0,0.6)",
      "text-anchor": "start"
    }
  }
});
// 1.初始化数据
const originalData = ref({
  // 节点
  nodes: [
    {
      id: "node1", // String，可选，节点的唯一标识
      shape: "rect", // 使用 rect 渲染 https://x6.antv.vision/zh/examples/node/native-node#rect
      x: 100, // Number，必选，节点位置的 x 值
      y: 200, // Number，必选，节点位置的 y 值
      width: 80, // Number，可选，节点大小的 width 值
      height: 40, // Number，可选，节点大小的 height 值
      label: "矩形" // String，节点标签
    },
    {
      id: "node2", // String，节点的唯一标识
      shape: "ellipse", // 使用 ellipse 渲染 https://x6.antv.vision/zh/examples/node/native-node#ellipse
      x: 300, // Number，必选，节点位置的 x 值
      y: 200, // Number，必选，节点位置的 y 值
      width: 80, // Number，可选，节点大小的 width 值
      height: 40, // Number，可选，节点大小的 height 值
      label: "椭圆" // String，节点标签
    },
    {
      id: "node3", // String，节点的唯一标识
      shape: "circle", // 使用 circle 渲染 https://x6.antv.vision/zh/examples/node/native-node#circle
      x: 500, // Number，必选，节点位置的 x 值
      y: 200, // Number，必选，节点位置的 y 值
      width: 80, // Number，可选，节点大小的 width 值
      height: 40, // Number，可选，节点大小的 height 值
      label: "圆形" // String，节点标签
    },

    {
      id: "node4", // String，节点的唯一标识
      shape: "polygon", // 使用 polygon 渲染 https://x6.antv.vision/zh/examples/node/native-node#polygon
      x: 700, // Number，必选，节点位置的 x 值
      y: 200, // Number，必选，节点位置的 y 值
      width: 80, // Number，可选，节点大小的 width 值
      height: 40, // Number，可选，节点大小的 height 值
      label: "多边形", // String，节点标签
      attrs: {
        body: {
          fill: "#efdbff",
          stroke: "#9254de",
          // 指定 refPoints 属性，多边形顶点随图形大小自动缩放
          // https://x6.antv.vision/zh/docs/api/registry/attr#refpointsresetoffset
          // refPoints 属性是一个字符串，表示多个顶点坐标的集合，以逗号分隔。每个顶点坐标由 x 和 y 值组成，以空格分隔。
          refPoints: "0,10 10,0 20,10 10,20"
        }
      }
    },
    {
      id: "node5", // String，节点的唯一标识
      shape: "path", // 使用 path 渲染 https://x6.antv.vision/zh/examples/node/native-node#path
      x: 900, // Number，必选，节点位置的 x 值
      y: 200, // Number，必选，节点位置的 y 值
      width: 80, // Number，可选，节点大小的 width 值
      height: 40, // Number，可选，节点大小的 height 值
      label: "路径", // String，节点标签
      path: "m250.56711,120.24085c-5.51067,-2.23535 -12.14789,-0.31602 -14.83385,4.30804c-2.67487,4.62406 -0.36627,10.18222 5.1444,12.42221c17.28674,7.04529 29.2182,21.86551 29.2071,39.0419c-0.0555,23.95214 -23.19697,43.33135 -51.79362,43.37317c-28.6022,-0.04647 -51.74368,-19.42568 -51.79917,-43.37317c-0.0111,-17.18569 11.92591,-32.01984 29.2293,-39.05119c5.52177,-2.23999 7.81372,-7.79816 5.1444,-12.42221c-2.67487,-4.61476 -9.30099,-6.54803 -14.82275,-4.30804c-24.70644,10.01491 -41.74345,31.24374 -41.75455,55.78144c0.0111,34.22266 33.12505,61.95769 74.00278,61.96699c40.87218,-0.00929 73.99168,-27.74433 73.99723,-61.96699c-0.0111,-24.52841 -17.02591,-45.74329 -41.72125,-55.77215zm-32.27598,45.70147c6.12666,0 11.09348,-4.15468 11.09348,-9.29458l0,-43.37782c0,-5.13526 -4.96682,-9.28994 -11.09348,-9.28994c-6.13776,0 -11.09903,4.15933 -11.09903,9.28994l0,43.37317c0,5.1399 4.96127,9.29923 11.09903,9.29923z"
    },

    {
      id: "node6", // String，节点的唯一标识
      shape: "polyline", // 使用 polyline 渲染 https://x6.antv.vision/zh/examples/node/native-node#polyline
      x: 1100, // Number，必选，节点位置的 x 值
      y: 200, // Number，必选，节点位置的 y 值
      width: 80, // Number，可选，节点大小的 width 值
      height: 40, // Number，可选，节点大小的 height 值
      label: "折线", // String，节点标签
      points: "0,0 0,10 10,10 10,0"
    },

    {
      id: "node7", // String，节点的唯一标识
      shape: "image", // 使用 image 渲染 https://x6.antv.vision/zh/examples/node/native-node#image
      x: 1300, // Number，必选，节点位置的 x 值
      y: 400, // Number，必选，节点位置的 y 值
      width: 80, // Number，可选，节点大小的 width 值
      height: 40, // Number，可选，节点大小的 height 值
      imageUrl: "https://gw.alipayobjects.com/os/s/prod/antv/assets/image/logo-with-text-73b8a.svg"
    },
    {
      id: "node8", // String，节点的唯一标识
      shape: "html", // 使用 html 内嵌入椭圆的图片 https://x6.antv.vision/zh/docs/tutorial/basic/cell#%E5%86%85%E7%BD%AE%E8%8A%82%E7%82%B9
      x: 500, // Number，必选，节点位置的 x 值
      y: 400, // Number，必选，节点位置的 y 值
      width: 80, // Number，可选，节点大小的 width 值
      height: 40, // Number，可选，节点大小的 height 值
      html() {
        const wrap = document.createElement("div");
        wrap.style.width = "100%";
        wrap.style.height = "100%";
        wrap.style.background = "red";
        wrap.style.display = "flex";
        wrap.style.justifyContent = "center";
        wrap.style.alignItems = "center";

        wrap.innerText = "我是html";

        // wrap.innerHTML = `
        //   <a href="#" class="my-btn">
        //     html
        //   </a>`;

        return wrap;
      }
    },

    {
      id: "node9", // String，节点的唯一标识
      shape: "text-block", // 使用 text-block 渲染 https://x6.antv.vision/zh/examples/node/native-node#text-block
      text: `There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable.`,
      attrs: {
        body: {
          fill: "#efdbff",
          stroke: "#9254de",
          rx: 4,
          ry: 4
        },
        // 文本节点可编辑
        label: {
          contenteditable: "true"
        }
      },
      x: 100, // Number，必选，节点位置的 x 值
      y: 400, // Number，必选，节点位置的 y 值
      width: 360, // Number，可选，节点大小的 width 值
      height: 120 // Number，可选，节点大小的 height 值
    },
    {
      id: "node10", // String，节点的唯一标识
      shape: "custom-rect",
      x: -400, // Number，必选，节点位置的 x 值
      y: 400, // Number，必选，节点位置的 y 值
      width: 200, // Number，可选，节点大小的 width 值
      height: 100 // Number，可选，节点大小的 height 值
    }
  ],

  // 边
  edges: [
    {
      shape: "edge", // https://x6.antv.vision/zh/docs/tutorial/basic/cell#%E5%86%85%E7%BD%AE%E8%BE%B9
      source: "node1", // String，必须，起始节点 id
      target: "node2" // String，必须，目标节点 id
    },
    {
      // shape: 'double-edge', // https://x6.antv.vision/zh/docs/tutorial/basic/cell#%E5%86%85%E7%BD%AE%E8%BE%B9
      source: "node2", // String，必须，起始节点 id
      target: "node3" // String，必须，目标节点 id
    },
    {
      // shape: 'shadow-edge', // https://x6.antv.vision/zh/docs/tutorial/basic/cell#%E5%86%85%E7%BD%AE%E8%BE%B9
      source: "node3", // String，必须，起始节点 id
      target: "node4" // String，必须，目标节点 id
    },
    {
      source: "node4", // String，必须，起始节点 id
      target: "node5" // String，必须，目标节点 id
    },
    {
      source: "node5", // String，必须，起始节点 id
      target: "node6" // String，必须，目标节点 id
    },
    {
      source: "node6", // String，必须，起始节点 id
      target: "node7" // String，必须，目标节点 id
    },
    {
      source: "node7", // String，必须，起始节点 id
      target: "node8" // String，必须，目标节点 id
    },
    {
      source: "node8", // String，必须，起始节点 id
      target: "node9" // String，必须，目标节点 id
    },
    {
      source: "node9", // String，必须，起始节点 id
      target: "node10" // String，必须，目标节点 id
    }
  ]
});
// 2.初始化画布
const init = () => {
  // 初始化 x6 图背景板
  // 创建一个新的 Graph 实例
  graph = new Graph({
    // 指定 Graph 的容器元素
    container: document.getElementById("x6Container")!,

    // 自动调整 Graph 的大小以适应容器
    autoResize: true,

    // 设置背景颜色
    background: {
      color: "#fffbe6" // 设置画布背景颜色
    },
    // 网格设置
    grid: {
      visible: true, // 渲染网格背景
      size: 10, // 网格大小 10px
      type: "doubleMesh",
      args: [
        {
          color: "#eee", // 主网格线颜色
          thickness: 1 // 主网格线宽度
        },
        {
          color: "#ddd", // 次网格线颜色
          thickness: 1, // 次网格线宽度
          factor: 4 // 主次网格线间隔
        }
      ]
    },

    // 滚动设置
    scroller: {
      enabled: false, // 启用滚动
      pannable: true // 允许通过拖动来平移视图
    },

    // 是否启用缩略图（小地图）
    minimap: false, // 禁用缩略图

    // 鼠标滚轮设置
    mousewheel: {
      enabled: true, // 启用鼠标滚轮缩放
      modifiers: ["ctrl", "meta"] // 可以通过按下 Ctrl 或 Meta 键来启用缩放
    },

    // 平移设置
    panning: {
      enabled: true, // 启用平移功能
      modifiers: "space" // 需要按住 space 键才能进行拖拽平移 大空格键
    },

    // 交互设置(拖拽移动)
    interacting: {
      nodeMovable: true
    },

    // 拖拽设置
    dragging: {
      enabled: true, // 启用拖拽功能
      ghost: false // 禁用拖拽时的虚影效果
    }
  });

  // 渲染
  graph.fromJSON(originalData.value);
  graph.zoomToFit(); // 缩放
  graph.scale(0.7); // 设置初始缩放比例，例如 0.7 表示缩小到 70%
  graph.centerContent(); // 居中
  graph.use(new Export()); // 加载导出插件
  graphInstance.value = graph; // 保存图实例
};

const handleExportSvg = () => {
  graph!.toSVG(
    (dataUri: string) => {
      // 下载
      DataUri.downloadDataUri(DataUri.svgToDataUrl(dataUri), "chart.svg");
    },
    {
      preserveDimensions: {
        width: 200,
        height: 200
      },
      stylesheet: `
                rect {
                  fill: red;
                }
              `,
      beforeSerialize: (svg: SVGSVGElement) => {
        console.log("我执行了", svg);
      }
    }
  );
};

const handleExportPng = () => {
  graph!.toPNG(
    (dataUri: string) => {
      // 下载
      DataUri.downloadDataUri(dataUri, "chart.png");
    },
    {
      backgroundColor: "red",
      padding: {
        top: 20,
        right: 30,
        bottom: 40,
        left: 50
      },
      quality: 1
    }
  );
};

onMounted(() => {
  init(); // 初始化树数据
});

/* ******************放大/缩小/全屏开始**************************************** */
const onEnlarge = () => {
  graphInstance.value.zoom(0.1);
};

const onNarrow = () => {
  graphInstance.value.zoom(-0.1);
};

const onFullScreen = () => {
  toggleFullScreen(containerRef.value);
};
/* ******************放大/缩小/全屏结束**************************************** */
</script>

<style scoped>
.tree-graph-container {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: rgb(239, 240, 245);
}

#x6Container {
  flex: 1;
  width: 100%;
  height: 100vh;
  position: relative;
}
#x6Container >>> .markupTest05Rect {
  fill: #2ecc71;
  stroke: #000;
}
</style>
